day-10 到 day-19 我們花了 10 天說明如何將 HTML 轉換成 AST, 經歷了下圖的流程
那當我們拿到 AST 之後,我們要怎麼像 Vue 一樣將它放到網頁中呢?
這就牽涉到該如何建立 HTML element 並將 child element 放到 parent element 中。
以下幾個重要的 POINT
下面我們來說明一下這些重要的 POINT,對應的 HTML API 各是什麼? 與如何使用他們
MDN 文件 createElement 的說明
createElement(tagName, is)
我們可以用以下方式建立 HTML element
const divElement = document.createElement('div');
// <div></div>
const pElement = document.createElement('p');
// <p></p>
const wordCountElement = createElement('p', { is: 'word-count' });
// <p is="word-count"></p> - 這個 element 是自訂的元素 ( word-count )
文字不算是一種 Element 因此需要使用 document.createTextNode(text) 來建立 text node
MDN 文件 setAttribute 的說明
setAttribute(name, value)
我們可以用以下方式在 HTML element 中添加屬性
const button = document.querySelector("button");
button.setAttribute("name", "helloButton"); // 追加 name 屬性
button.setAttribute("disabled", ""); // 追加 disabled 屬性
// <button name="helloButton" disabled></button>
MDN 文件 appendChild 的說明
appendChild(aChild)
我們可以用以下方式附加子元素到 HTML element 中
const p = document.createElement("p");
document.body.appendChild(p);
/*
<body>
<p></p>
</body>
*/
const ul = document.createElement("ul");
const li01 = document.createElement("li");
const li02 = document.createElement("li");
ul.appendChild(li01);
ul.appendChild(li02);
/*
<ul>
<li></li>
<li></li>
</ul>
*/
const textNode = document.createTextNode("首頁");
li01.appendChild(textNode);
/*
<ul>
<li>首頁</li>
<li></li>
</ul>
*/
建立 HTML DOM 其實就是將 AST 遍歷一次,並做對應的 createElement, setAttribute, appendChild 動作。
既然如此我們就直接拿 day-19 的 BFS 或是 DFS 來用
並且修改一下 transformer,讓它可以建立 HTML DOM
const transformer = () => {
const appender = node => {
// console.log('node=',node);
// ROOT 節點
if (node.type === 'root') {
const root = document.createElement('template');
root.setAttribute('data-type', 'root');
node.element = root;
node.children?.forEach(child => child.parentElement = root);
}
// text 節點
else if (node.type === 'text') {
const textNode = document.createTextNode(node.content);
node.parentElement.appendChild(textNode);
}
// element 節點
else {
const element = document.createElement(node.type);
node.element = element;
// 追加屬性
node.attrs?.forEach(attr => element.setAttribute(attr.name, attr.value));
// 提示父元素
node.children?.forEach(child => child.parentElement = node.element);
// 將 element 附加到父元素上
node.parentElement?.appendChild(element);
}
return node;
}
return {
appender,
}
};
export function createRenderer() {
const {appender} = transformer();
return {
render: ast => dfs(ast, appender),
};
}
複習一下昨天最後的 newAST 結果
const newAST = {
"type": "root",
"children": [
{
"type": "div",
"attrStr": "id=\"app\"",
"children": [
{
"type": "p",
"attrStr": "class=\"text-red\"",
"children": [
{
"type": "text",
"content": "Vue"
}
],
"attrs": [
{
"name": "class",
"value": "text-red"
}
]
},
{
"type": "input",
"attrStr": "type=\"text\" id=\"username\" placeholder=\"請輸入姓名\" disabled",
"attrs": [
{
"name": "type",
"value": "text"
},
{
"name": "id",
"value": "username"
},
{
"name": "placeholder",
"value": "請輸入姓名"
},
{
"name": "disabled",
"value": true
}
]
},
{
"type": "img",
"attrStr": "src=\"https://ithelp.ithome.com.tw/storage/image/fight.svg\"\r\n alt='\"圖片\"'",
"attrs": [
{
"name": "src",
"value": "https://ithelp.ithome.com.tw/storage/image/fight.svg"
},
{
"name": "alt",
"value": "\"圖片\""
}
]
},
{
"type": "p",
"attrStr": "style=\"margin-top: 3px\"",
"children": [
{
"type": "text",
"content": "\"Template\""
}
],
"attrs": [
{
"name": "style",
"value": "margin-top: 3px"
}
]
}
],
"attrs": [
{
"name": "id",
"value": "app"
}
]
}
]
}
利用上面的 renderer 就可以將昨天的 newAST 生成 HTML DOM 附加到網頁的 body 中
const renderer = createRenderer();
const body = document.querySelector("body");
const dom = renderer.render(newAST);
const childrenElements = Array.from(dom.element.children);
childrenElements.forEach(child => body.appendChild(child));
今天我們說明了如何將 AST 轉換成 HTML DOM,並且附加到 body 中。
相信邦友們也比較了解為何花那麼長的篇幅說明 parser 跟 AST 了吧!ヽ(✿゚▽゚)ノ